﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace pulsarc.Language
{
    class Compiler
    {
        private IList<object> tokens;
        /// <summary>
        /// Contains Variables and other
        /// data.
        /// </summary>
        private StringBuilder Header = new StringBuilder();
        /// <summary>
        /// Contains the code that will actually make the os
        /// start
        /// </summary>
        private StringBuilder Boot = new StringBuilder();
        /// <summary>
        /// Conatins the actual code.
        /// </summary>
        private StringBuilder Main = new StringBuilder();
        /// <summary>
        /// All the Methods.
        /// </summary>
        private List<PMethod> Methods = new List<PMethod>();
        /// <summary>
        /// All the Classes.
        /// </summary>
        private List<PClass> Classes = new List<PClass>();
        /// <summary>
        /// All the Variables. In every class, and or
        /// void. Btw their name, is like this ParentName+ActualName
        /// if its in a void, its ParentName+MethodName+ActualName
        /// </summary>
        private List<PVariable> Variables = new List<PVariable>();
        /// <summary>
        /// Current Token Index.
        /// </summary>
        private int index = 0;
        /// <summary>
        /// Current Method.
        /// </summary>
        private string currentMethod = "";
        /// <summary>
        /// Current Package.
        /// </summary>
        private string currentPackage = "";
        /// <summary>
        /// Adds asm to the Main String Builder.
        /// </summary>
        /// <param name="asm"></param>
        private void addASM(string asm)
        {
            Main.Append(asm + "\n");
        }
        /// <summary>
        /// Peeks a token without advancing
        /// </summary>
        /// <returns></returns>
        private object PeekToken()
        {
            return tokens[index];
        }
        /// <summary>
        /// Peeks a token a few tokens ahead of the index.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        private object PeekToken(int offset)
        {
            if (index > tokens.Count)
                return 0;
            return tokens[index + offset];
        }
        /// <summary>
        /// Reads a token and advances the position by one
        /// </summary>
        /// <returns></returns>
        private object ReadToken()
        {
            index++;
            if (index > tokens.Count)
                return 0;
            return tokens[index - 1];
        }
        /// <summary>
        /// Creates a new instance of the Pulsar compiler
        /// </summary>
        /// <param name="input_file">The file containing the source code</param>
        public Compiler(string code)
        {
            Scanner scan = new Scanner(new StringReader(code));
            tokens = scan.Tokens;
        }
        /// <summary>
        /// Finds all methods in the code.
        /// </summary>
        private void FindMethodsAndFunctions()
        {
            string package = "";
            while (index < tokens.Count)
            {
                if (PeekToken().ToString().ToLower() == "void")
                {
                    ReadToken();
                    string name = ReadToken().ToString();
                    PMethod m = new PMethod();
                    m.Class_Name = package;
                    m.Name = name;
                    Methods.Add(m);
                    Console.WriteLine(DateTime.Now.ToString() + " Found Void:" + name);
                }
            
                else if (PeekToken().ToString().ToLower() == "package")
                {
                    ReadToken();
                    string name = "";
                    while (true)
                    {
                        string bit = ReadToken().ToString();
                        name += bit;
                        if (PeekToken() == Scanner.Dot)
                        {
                            ReadToken();
                            name += ".";
                        }
                        else
                        {
                            break;
                        }
                    }
                    package = name;
                    PClass c = new PClass();
                    c.Name = name;
                    Classes.Add(c);
                    Console.WriteLine(DateTime.Now.ToString() + " Found Package:" + name);
                }
      
                else
                {
                    //Skip the token, as its not important.
                    ReadToken();
                }
            }
            //Reset index so that we can do ReadToken again
            //to actually compile down the code.
            index = 0;
        }
        /// <summary>
        /// Checks to see if the next
        /// token is a known method.
        /// </summary>
        /// <returns></returns>
        private bool isMethod()
        {
            foreach (PMethod f in Methods)
            {
                if (f.Name == PeekToken().ToString())
                {
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Compiles all the source code down.
        /// </summary>
        /// <returns></returns>
        /// 
        public string Compile()
        {
            Console.WriteLine(DateTime.Now.ToString() + " Compiling.");
            //Scans and maps out all voids and packages.
            FindMethodsAndFunctions();
            //Now actually compile down the code.
            while (index < tokens.Count)
            {
                if (PeekToken().ToString().ToLower() == "package")
                {
                    ReadToken();
                    string name = "";
                    while (true)
                    {
                        string bit = ReadToken().ToString();
                        name += bit;
                        if (PeekToken() == Scanner.Dot)
                        {
                            ReadToken();
                            name += ".";
                        }
                        else
                        {
                            break;
                        }
                    }
                    currentPackage = name;
                    CompilePackage();
                }
            }
            //Now form our output file.
            #region " Form Output.asm "
            Console.WriteLine(DateTime.Now.ToString() + " Done Compiling.");
            //Build the boot code.
            Boot.Append("jmp START ;Jump to the start of the os.\n");
            Boot.Append("START: ;Starting point of the whole system.\n");
            for (int i = 0; i < Classes.Count; i++)
            {
                if (Classes[i].Boot)
                {
                    Boot.Append("call " + Classes[i].Name + ".Main ;Call the main void of the code, to start the kernel/os.\n");
                    break;
                }
            }
            //Now once the os is done, it will return so do a jmp to hang, otherwise the whole CPU will fualt
            //Im not sure why, but it does.
            Boot.Append("jmp HANG ;Hang the whole system.\n");
            //Now on the last part of the main code, add the hang label.
            Main.Append("HANG:\n");
            Main.Append("jmp HANG ;Infinte Loop.\n");
            //Now add our variables.
            for (int i = 0; i < Variables.Count; i++)
            {
                Header.Append(Variables[i].Assembly_Name + ": db ");
                if (Variables[i].DataType == PVariable.Type.Byte || Variables[i].DataType == PVariable.Type.Bool)
                {
                    Header.Append("0");
                }
                else if (Variables[i].DataType == PVariable.Type.Short)
                {
                    Header.Append("0,0");
                }
                else if (Variables[i].DataType == PVariable.Type.Integer)
                {
                    Header.Append("0,0,0,0");
                }
                Header.Append("\n");
            }
            //Build the Output.asm file.
            //Add a little message, just for the fun of it.
            return ";Pulsar Platform, Defining The Limits.\n" + BootHeader.Data + Header.ToString() + Boot.ToString() + Main.ToString();
            #endregion
        }
        /// <summary>
        /// Compiles a package
        /// </summary>
        public void CompilePackage()
        {
            while (PeekToken().ToString() != "end")
            {
                #region " void "
                if (PeekToken().ToString().ToLower() == "void")
                {
                    ReadToken(); // Goto next token
                    if (PeekToken() is string)
                    {
                        string name = ReadToken().ToString();
                        //Check to see if the void is bootable.
                        //In otherwords if its the start of the whole os.
                        if (name.ToLower() == "main")
                        {
                            Console.WriteLine(DateTime.Now.ToString() + " Found Bootable Void In " + currentPackage);
                            //Tell the parent class its bootable so that
                            //our compiler will generate a call statement
                            //so the os starts.
                            for (int i = 0; i < Classes.Count; i++)
                            {
                                if (Classes[i].Name == currentPackage)
                                {
                                    Classes[i].Boot = true;
                                    break;
                                }
                            }
                        }
                        //Now add a label for this void, but include its holders name.
                        //So the parent packages name.
                        addASM(currentPackage + "." + name + ": ;Orginal was a void.");
                        //now add pop edi, because we want to move the return address from
                        //the stack to keep it safe, just dont use edi for a register in our
                        //compiler that way we don't mess things up . =)
                        addASM("pop edi ;Save the return address.");
                        //Set the current method.
                        currentMethod = name;
                        //Now compile it down..
                        CompileMethod();
                    }
                    else
                    {
                        throw new Exception("unreconized input!");
                    }
                }
                #endregion
              
              
                #region " variable "
                else if (PeekToken().ToString().ToLower() == "define")
                {
                    ReadToken();
                    string type = ReadToken().ToString();
                    string name = ReadToken().ToString();
                    PVariable pvar = new PVariable();
                    if (type.ToLower() == "byte")
                    {
                        pvar.DataType = PVariable.Type.Byte;
                    }
                    else if (type.ToLower() == "integer")
                    {
                        pvar.DataType = PVariable.Type.Integer;
                    }
                    else if (type.ToLower() == "short")
                    {
                        pvar.DataType = PVariable.Type.Short;
                    }
                    else if (type.ToLower() == "bool")
                    {
                        pvar.DataType = PVariable.Type.Bool;
                    }
                    pvar.Name = name;
                    pvar.Assembly_Name = currentPackage + "." + name;
                    Variables.Add(pvar);
                    Console.WriteLine(DateTime.Now.ToString() + " Found Variable " + name + " In " + currentPackage);
                }
                else
                {
                    throw new Exception("Compiler encountered unkown token: " + PeekToken().ToString());
                }
                #endregion
            }
            ReadToken();
        }
        /// <summary>
        /// Compiles a Method's Code.
        /// </summary>
        public void CompileMethod()
        {
            //Note: What happens if we have a if statement?
            //I gues we would have to just compile it down knowing it would
            //have a end, but could we do 'end if'? 
            // Re: Note
            // End If is unnessor
            if (ReadToken() != Scanner.OpenPar)
                throw new Exception("Expected ( Bad Token:" + PeekToken(-1).ToString());
            // Parse parametes
            while (PeekToken() != Scanner.ClosePar)
            {

            }
            ReadToken(); // read the )
            while (PeekToken().ToString().ToLower() != "end")
            {
                CompileLine();
            }
            ReadToken();
            //Now before ret, we need to push edi, as edi is
            //used to hold the return address. This way
            //we can change the stack but still be able to
            //return.
            addASM("push edi ;Restore the return address");
            addASM("ret ;Return to caller");
            //Now set currentmethod to nothing as we are no longer in a method.
            currentMethod = "";
        }
        /// <summary>
        /// Passes parameters (In between ( and ) )
        /// </summary>
        private void PassParameters()
        {
            if (ReadToken() != Scanner.OpenPar)
                throw new Exception("Expected (");
            // Parse parametes
            while (PeekToken() != Scanner.ClosePar)
            {

            }
            ReadToken(); // read the )
        }
        /// <summary>
        /// Compiles code between methods.
        /// </summary>
        private void CompileLine()
        {
            if (PeekToken().ToString() == "return")
            {

                addASM("ret");
            }
            else if (PeekToken().ToString() == "int")
            {
                ReadToken();
                string name = ReadToken().ToString();
                if (ReadToken() != Scanner.Assign)
                {
                    Header.Append("_" + currentMethod + "__" + name + " dd 0\n");
                }
                else
                {
                    Header.Append("_" + currentMethod + "__" + name + " dd " + ReadToken().ToString() + "\n");
                }

            }
            else if (isMethod())
            {
                string op1 = ReadToken().ToString();

                PassParameters();
                addASM("call " + op1);

            }
            else if (PeekToken().ToString().ToLower() == "define")
            {
                ReadToken();
                string type = ReadToken().ToString();
                string name = ReadToken().ToString();
                PVariable pvar = new PVariable();
                if (type.ToLower() == "byte")
                {
                    pvar.DataType = PVariable.Type.Byte;
                }
                else if (type.ToLower() == "integer")
                {
                    pvar.DataType = PVariable.Type.Integer;
                }
                else if (type.ToLower() == "short")
                {
                    pvar.DataType = PVariable.Type.Short;
                }
                else if (type.ToLower() == "bool")
                {
                    pvar.DataType = PVariable.Type.Bool;
                }
                pvar.Name = name;
                pvar.Assembly_Name = currentPackage + "." + currentMethod + "." + name;
                Variables.Add(pvar);
                Console.WriteLine(DateTime.Now.ToString() + " Found Variable " + name + " In " + currentPackage);
            }
            else if (PeekToken().ToString() == "package") this.currentPackage = ReadToken().ToString();
            else
            {
                Console.WriteLine("Unkown Token:" + ReadToken().ToString());
            }

        }
    }
}
